package com.javaweb.config.redis;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.javaweb.annotation.cache.RedisCache;
import com.javaweb.constant.CommonConstant;
import com.javaweb.constant.SystemConstant;
import com.javaweb.constant.SystemConstant.RedisConstant;
import com.javaweb.util.core.FileUtil;

import io.lettuce.core.api.StatefulConnection;

/**
 * redis配置
 * @author tank2140896
 * <p>
 * 常用命令：
 * 字符串：get、mget、set、mset、getset、del、strlen、append、incr、incrby、decr、decrby、incrbyfloat、decrbyfloat
 * 散列：hset、hget、hincrby、hincrbyfloat、hexists、hdel、hlen、hmset、hmget、hkeys、hvals、hgetall
 * 列表：lpush、rpush、lpushx、rpushx、lpop、rpop、rpoplpush、llen、lindex、lrange（所有就是0到-1）、lset、linsert、ltrim、lrem、blpop、brpop、brpoplpush
 * 集合：sadd、srem、smove、smembers、scard、sismember、srandmember、spop、sinter（交集）、sinterstore（交集）、sunion（并集）、sunionstore（并集）、sdiff（差集）、sdiffstore（差集）
 * 有序集合：zadd、zrem、zscore、zincrby、zcard、zrank、zrevrank、zrange、zrevrange、zrangebyscore、zrevrangebyscore、zcount、zremrangebyrank、zremrangebyscore、zunionstore、zinterstore、zrangebylex、zrevrangebylex、zlexcount、zremrangebylex、zpopmax、zpopmin、bzpopmax、bzpopmin
 * 集合计算：pfadd、pfcount、pfmerge（并集）
 * 位图：setbit、getbit、bitcount、bitpos、bitop（位运算）、bitfield
 * 坐标：geoadd、geopos、geodist、georadius、georadiusbymember、geohash
 * 流：xadd、xtrim、xdel、xrange、xrevrange、xread、xgroup、xreadgroup、xpending、xinfo、xack、xclaim
 * 数据库：keys、[h/s/z]scan、sort、exists、dbsize、type、rename、renamenx、move、del、unlink、flushdb、flushall、swapdb
 * 过期：expire、pexpire、expireat、pexpireat、ttl、pttl
 * 事务：multi、exec、discard、watch、unwatch
 * 持久化：save、bgsave、shutdown
 * 发布与订阅：publish、subscribe、unsubscribe、psubscribe、punsubscribe、pubsub
 */
@Configuration
@EnableCaching
public class RedisConfig implements CachingConfigurer {
	
	@Autowired
	private RedisParameter redisParameter;
	
	//redis连接
	public RedisConnectionFactory getRedisConnectionFactory() {
		var redisStandaloneConfiguration = new RedisStandaloneConfiguration();
		redisStandaloneConfiguration.setHostName(redisParameter.getHost());
		redisStandaloneConfiguration.setPassword(redisParameter.getPassword());
		redisStandaloneConfiguration.setPort(redisParameter.getPort());
		GenericObjectPoolConfig<StatefulConnection<?,?>> genericObjectPoolConfig = new GenericObjectPoolConfig<>();
		genericObjectPoolConfig.setMinIdle(redisParameter.getMinIdle());
		genericObjectPoolConfig.setMaxIdle(redisParameter.getMaxIdle());
		genericObjectPoolConfig.setMaxTotal(redisParameter.getMaxTotal());
		genericObjectPoolConfig.setMaxWait(Duration.ofMillis(redisParameter.getMaxWaitMillis()));
		LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(redisParameter.getCommandTimeout())).poolConfig(genericObjectPoolConfig).build();
		var factory = new LettuceConnectionFactory(redisStandaloneConfiguration,lettuceClientConfiguration);
		//factory.setDatabase(0);//默认0（可选值：0-15）
		//factory.setValidateConnection(true);//连接校验
		factory.afterPropertiesSet();//必须初始化实例
		return factory;
	}
	
	//StringRedisTemplate
    @Bean("redisTemplate")
	public StringRedisTemplate redisTemplate(){
		var objectMapper = new ObjectMapper();
		objectMapper.setVisibility(PropertyAccessor.ALL,Visibility.ANY);
		objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(objectMapper,Object.class);
		//jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
		var stringRedisTemplate = new StringRedisTemplate(getRedisConnectionFactory());
		stringRedisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
		stringRedisTemplate.afterPropertiesSet();
		return stringRedisTemplate;
	}
    
    //redis消息监听
    @Bean
    public RedisMessageListenerContainer container(@Qualifier("listenerAdapter")MessageListenerAdapter messageListenerAdapter) {
    	var container = new RedisMessageListenerContainer();
        container.setConnectionFactory(getRedisConnectionFactory());
        //可以添加多个 messageListener，配置不同的交换机。但是如果是多个的话，接收消息时就需要判断不同的主题进行不同的业务逻辑处理
        container.addMessageListener(messageListenerAdapter,new PatternTopic(RedisConstant.REDIS_CONFIG_TOPIC));
        container.addMessageListener(messageListenerAdapter,new PatternTopic(RedisConstant.REDIS_DICTIONARY_TOPIC));
        container.addMessageListener(messageListenerAdapter,new PatternTopic(RedisConstant.REDIS_INTERFACES_TOPIC));
        return container;
    }
    
    //redis消息监听适配
    @Bean("listenerAdapter")
    public MessageListenerAdapter listenerAdapter(RedisMqReceive redisMqReceive) {
        return new MessageListenerAdapter(redisMqReceive,"onMessage");
    }
    
    private final String protocolName = "file";
    //缓存管理器
    @Bean
    public RedisCacheManager redisCacheManager(StringRedisTemplate stringRedisTemplate) {
    	RedisCacheManagerBuilder build = RedisCacheManagerBuilder.
    			fromConnectionFactory(stringRedisTemplate.getConnectionFactory()).
    			cacheDefaults(getRedisCacheConfiguration(stringRedisTemplate,60*60));//默认1小时
    			//withCacheConfiguration("cache_user",getRedisCacheConfiguration(stringRedisTemplate,60))
    	try{
        	Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(SystemConstant.DEFAULT_GLOBAL_CONTROLLER_PATH);
    		while(urls.hasMoreElements()){
    			URL url = urls.nextElement();
    			String protocol = url.getProtocol();
    			if(protocolName.equals(protocol)){
    				String filePath = URLDecoder.decode(url.getFile(),StandardCharsets.UTF_8);
    				List<File> fileList = new ArrayList<>();
    				fileList = FileUtil.getAllFilesName(new File(filePath),fileList,true,false);
    				for(int i=0;i<fileList.size();i++){
    					String className = SystemConstant.DEFAULT_GLOBAL_CONTROLLER_PACKAGE + CommonConstant.DOT + fileList.get(i).getName().replace(".class",CommonConstant.EMPTY_VALUE);
    					Class<?> cls = Class.forName(className);
    					Method[] methods = cls.getDeclaredMethods();
    					for(int j=0;j<methods.length;j++){
    						RedisCache redisCache = methods[j].getAnnotation(RedisCache.class);
    						if(redisCache!=null){
    							for(String value:redisCache.value()){
    								build.withCacheConfiguration(value,getRedisCacheConfiguration(stringRedisTemplate,redisCache.expireSeconds()));
    							}
    						}
    					}
    				}
    			}
    		}
    	}catch(Exception e){
    		//do nothing
    	}
		RedisCacheManager redisCacheManager = build.transactionAware().build();
    	return redisCacheManager;
	}
    
    //缓存配置
    private RedisCacheConfiguration getRedisCacheConfiguration(StringRedisTemplate stringRedisTemplate,long seconds){
    	return RedisCacheConfiguration.defaultCacheConfig().
    		   serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisTemplate.getStringSerializer())).
    		   serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisTemplate.getValueSerializer())).
    		   disableCachingNullValues().//不缓存null
    		   entryTtl(Duration.ofSeconds(seconds <= 0 ? Long.MAX_VALUE : seconds));
    }
    
    //定义缓存数据key生成策略（形如com.javaweb.web.controller.TestController#getTest）
    @Override
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
        	@Override
			public Object generate(Object target, Method method, Object... params) {
        		var stringBuilder = new StringBuilder();
				stringBuilder.append(target.getClass().getName());
				stringBuilder.append(CommonConstant.POUND);
				stringBuilder.append(method.getName());
				stringBuilder.append(CommonConstant.POUND);
                for (Object obj : params) {
                	stringBuilder.append(getRequestParams(obj));
                }
                return stringBuilder.toString();
			}
        };  
    }
    
    //获得请求参数
    private String getRequestParams(Object obj) {
    	String out = CommonConstant.EMPTY_VALUE;
    	try {
    		//默认都应该是json字符串
    		out = new ObjectMapper().writeValueAsString(obj);
    	}catch(Exception e) {
    		//do nothing
    	}
    	return out;
    }
    
}
